//Created by wcjohns on 20130502.
//  A horrible, poorly coded, hack to track carpool

#include <sstream>
#include <fstream>
#include <iostream>

#include <Wt/WString>
#include <Wt/Dbo/Dbo>
#include <Wt/Dbo/ptr>
#include <Wt/WComboBox>
#include <Wt/WGroupBox>
#include <Wt/WDateTime>
#include <Wt/WGoogleMap>
#include <Wt/WTabWidget>
#include <Wt/Dbo/Session>
#include <Wt/WJavaScript>
#include <Wt/WPushButton>
#include <Wt/WGridLayout>
#include <Wt/WButtonGroup>
#include <Wt/WRadioButton>
#include <Wt/WApplication>
#include <Wt/WEnvironment>
#include <Wt/Dbo/WtSqlTraits>
#include <Wt/WContainerWidget>
#include <Wt/Dbo/backend/Sqlite3>

#include <boost/foreach.hpp>
#include <boost/date_time.hpp>
#include <boost/filesystem/path.hpp>
#define foreach         BOOST_FOREACH

using namespace Wt;
using namespace std;

/*
TODO List:
  -Add a table to view TripSummarry entries in the database
  -Histogram to show drive time of each driver (select morning, eveing, both)
  -Graph showing speed of drive verses distance
  -blah, blah, blah
*/

//Forward declarations
Wt::WApplication *createApplication( const Wt::WEnvironment& env );

//How often to push updates to the client (e.g. update the map), in seconds
#define PUSH_UPDATE_DT 60

//How often the client should transmit the postion to the server, in milli-seconds
#define GPS_UPDATE_DT std::string("3000")


//The functions defined by WT_DECLARE_WT_MEMBER functions are actually
//  javascript, not c++
WT_DECLARE_WT_MEMBER
(GetLocation, Wt::JavaScriptFunction, "GetLocation",
 function( sender )
{
  /* http://diveintohtml5.info/geolocation.html */
  if( navigator.geolocation )
  {
    var emitterfcn = function(postion)
    {
      var c = postion.coords;
      var t = postion.timestamp;
      var lat = (c.latitude)?c.latitude:-Infinity;
      var lon = (c.longitude)?c.longitude:-Infinity;
      var alt = (c.altitude)?c.altitude:-Infinity;
      var acc = (c.accuracy)?c.accuracy:-Infinity;
      var aacc = (c.altitudeAccuracy)?c.altitudeAccuracy:-Infinity;
      Wt.emit(sender,'Location',lat,lon,alt,acc,aacc,t);
    };
    var handle_error = function(code,message){ Wt.emit(sender,'Location',
                   -Infinity, -Infinity, -Infinity, -Infinity, -Infinity,0 ); };
    navigator.geolocation.getCurrentPosition( emitterfcn, handle_error,
                                    {enableHighAccuracy: true, maximumAge: 0} );
  }else
  {
    Wt.emit(sender,'Location', -Infinity, -Infinity, -Infinity,
                                                    -Infinity, -Infinity,0 );
  }
}
);


WT_DECLARE_WT_MEMBER
(GetReOccurringLocation, Wt::JavaScriptFunction, "GetReOccurringLocation",
 function( sender, dt )
{
  Wt.WT.GetLocation(sender);
  var refreshIntervalId = setInterval(function(){Wt.WT.GetLocation(sender)},dt);
  $('#'+sender).data('refreshid', refreshIntervalId);
}
);


WT_DECLARE_WT_MEMBER
(StopReOccurringLocation, Wt::JavaScriptFunction, "StopReOccurringLocation",
 function( sender )
{
  var refreshid = $('#'+sender).data('refreshid');
  if( refreshid )
  {
    clearInterval( refreshid );
    $('#'+sender).data('refreshid', null);
  }
}
);

enum Driver
{
  Joe, Mike, Rick, Will, NumDrivers
};//enum Driver

const char *to_str( const Driver d )
{
  switch( d )
  {
    case Joe:  return "Joe";
    case Mike: return "Mike";
    case Rick: return "Rick";
    case Will: return "Will";
    case NumDrivers: return "";
  }//switch( d )
  return "";
}//const char *to_str( Driver d )


boost::posix_time::ptime from_utc_to_ca_local_time( const boost::posix_time::ptime &utc_time )
{
  static const boost::local_time::time_zone_ptr post2006_zone( new boost::local_time::posix_time_zone("PST-8PDT01:00:00,M4.1.0/02:00:00,M10.1.0/02:00:00") );
  return boost::local_time::local_date_time( utc_time, post2006_zone ).local_time();
}//std::string from_utc_to_ca_local_time( boost::posix_time::ptime utc_time )


WDateTime now()
{
//  return WDateTime::fromPosixTime(boost::posix_time::second_clock::local_time());
  namespace pt = boost::posix_time;
  const pt::ptime utctime = boost::posix_time::second_clock::universal_time();
  const pt::ptime localtime = from_utc_to_ca_local_time( utctime );
  return WDateTime::fromPosixTime( localtime );
}//WDateTime now()



struct PositionPoint;  //forward declartation

struct TripSummarry
{
  WDateTime startTime, endTime;
  WString driver;
  WString passengers;  // bitwise 'or' of (0x1<<Driver)
  WString direction;
  
  double recordedDistance; //in miles
  double totalTime;     //in minutes
  double minSpeed;      //mph
  double avrgSpeed;     //mph
  double maxSpeed;      //mph

  TripSummarry()
  : recordedDistance(0.0), totalTime(0.0),
    minSpeed(0.0), avrgSpeed(0.0), maxSpeed(0.0)
  {
  }
  
  template<class Action>
  void persist(Action& a)
  {
    Wt::Dbo::field( a, startTime,     "StartTime" );
    Wt::Dbo::field( a, endTime,       "EndTime" );
    Wt::Dbo::field( a, driver,        "Driver" );
    Wt::Dbo::field( a, passengers,    "Passengers" );
    Wt::Dbo::field( a, direction,     "Direction" );
    Wt::Dbo::field( a, recordedDistance, "RecordedDistance" );
    Wt::Dbo::field( a, totalTime,     "TotalTime" );
    Wt::Dbo::field( a, minSpeed,      "MinSpeed" );
    Wt::Dbo::field( a, avrgSpeed,     "AvrgSpeed" );
    Wt::Dbo::field( a, maxSpeed,      "MaxSpeed" );
  }//persist(...)
};//struct TripSummarry


struct ClientSession
{
  WDateTime startTime, endTime;
  Driver driver;
  int passengers;  // bitwise 'or' of (0x1<<Driver)
  
  Wt::Dbo::collection< Wt::Dbo::ptr<PositionPoint> > PositionPoints;
  
  ClientSession()
  : startTime( now() ),
    endTime(), driver( NumDrivers ), passengers(0)
  {
  }
  
  template<class Action>
  void persist(Action& a)
  {
    Wt::Dbo::field( a, startTime,  "StartTime" );
    Wt::Dbo::field( a, endTime,    "EndTime" );
    Wt::Dbo::field( a, driver,     "Driver" );
    Wt::Dbo::field( a, passengers, "Passengers" );
    Wt::Dbo::hasMany( a, PositionPoints, Wt::Dbo::ManyToOne, "PositionPoints" );
  }//persist(...)
};//struct ClientSession


struct PositionPoint
{
  WDateTime time;
  double longitude;
  double latitude;
  double locationUncert;
  Wt::Dbo::ptr<ClientSession> ClientSessionPtr;
  
  template<class Action>
  void persist(Action& a)
  {
    Wt::Dbo::field( a, time, "Time" );
    Wt::Dbo::field( a, longitude, "Longitude" );
    Wt::Dbo::field( a, latitude, "Latitude" );
    Wt::Dbo::field( a, locationUncert, "LocationUncert" );
    Wt::Dbo::belongsTo( a, ClientSessionPtr, "ClientSession" );
  }//persist(...)
};//struct DetectorSubmitSummarry




class Carpool : public WContainerWidget
{
protected:
  WTabWidget *m_tab;
  WDateTime m_lastMapPush;
  Wt::WGoogleMap *m_map;
  Wt::WPushButton *m_startTrip, *m_stopTrip;
  WComboBox *m_driver;
  vector<WRadioButton *> m_passengers;
  vector<WGoogleMap::Coordinate> m_currentPath;
  Dbo::ptr<ClientSession> m_currentTrip;
  Dbo::backend::Sqlite3 m_database;
  Dbo::Session m_dbSession;
  Wt::JSignal<double /*Latitude*/,
              double /*Longitude*/,
              double /*Altitude*/,
              double /*Pos Accuracy*/,
              double /*Altitude Accuracy*/,
              double /*timestamp*/> m_locationSignal;
  
public:
  Carpool( WContainerWidget *parent = 0 )
  : WContainerWidget( parent ),
    m_tab( 0 ),
    m_map( 0 ),
    m_startTrip( 0 ),
    m_stopTrip( 0 ),
    m_driver( 0 ),
    m_passengers( NumDrivers, (WRadioButton *)0 ),
    m_database( "locations.db"),
    m_locationSignal( this, "Location" )
  {
    WApplication *app = WApplication::instance();
    
    LOAD_JAVASCRIPT(app, "main.cpp", "Carpool", wtjsGetLocation);
    LOAD_JAVASCRIPT(app, "main.cpp", "Carpool", wtjsGetReOccurringLocation);
    LOAD_JAVASCRIPT(app, "main.cpp", "Carpool", wtjsStopReOccurringLocation);
    
    m_dbSession.setConnection( m_database );
    m_dbSession.mapClass<ClientSession>( "ClientSession" );
    m_dbSession.mapClass<PositionPoint>( "PositionPoint" );
    m_dbSession.mapClass<TripSummarry>( "TripSummarry" );
    
    try
    {
      m_dbSession.createTables();
    }catch( std::exception &e )
    {
    }//try / catch to create the database tables
    
    m_locationSignal.connect( this, &Carpool::setLocationFromGps );
    
    WGroupBox *passengerBox = new WGroupBox( "Passengers" );
    m_driver = new WComboBox();
    
    for( Driver d = Driver(0); d < NumDrivers; d = Driver(d+1) )
    {
      m_driver->addItem( to_str(d) );
      m_passengers[d] = new WRadioButton( to_str(d), passengerBox );
    }//for( Driver d = Driver(0); d < NumDrivers; d = Driver(d+1) )
    
    try
    {
      //Get last trip, and set defaults off of this
      Dbo::Transaction transaction( m_dbSession );
      Dbo::ptr<ClientSession> lastTrip = m_dbSession.find<ClientSession>()
                                               .orderBy("EndTime DESC")
                                               .limit(1);
      if( lastTrip )
      {
        int advance = 1;
        if( lastTrip->startTime.date() == now().date() )
          advance = 0;
        
        const int driver = ((lastTrip->driver+advance) % NumDrivers);
        m_driver->setCurrentIndex( driver );
        for( Driver d = Driver(0); d < NumDrivers; d = Driver(d+1) )
          if( lastTrip->passengers & (1<<d) )
            m_passengers[d]->setChecked();
        m_passengers[driver]->setChecked();
        m_passengers[driver]->setHidden( true );
      }//if( lastTrip )
      
      transaction.commit();
    }catch( exception &e )
    {
      cerr << "Failed to get previous driver/riders: " << e.what() << endl;
      m_currentTrip.reset();
    }//try / catch
    
    WGridLayout *layout = new WGridLayout();
    setLayout( layout );
    m_tab = new WTabWidget();
    layout->addWidget( m_tab, 0, 0 );
    
    
    WContainerWidget *mapTab = new WContainerWidget();
    m_tab->addTab( mapTab, "Current Trip" );
    WGridLayout *maplayout = new WGridLayout();
    mapTab->setLayout( maplayout );
    m_map = new WGoogleMap( WGoogleMap::Version3 );
    m_map->setInline( false );
    maplayout->addWidget( m_map, 0, 0 );
    m_startTrip = new WPushButton( "Start Trip" );
    m_stopTrip = new WPushButton( "Stop Trip" );
    m_stopTrip->disable();
    m_startTrip->clicked().connect( this, &Carpool::startTrip );
    m_stopTrip->clicked().connect( this, &Carpool::stopTrip );
    
    WContainerWidget *buttonBar = new WContainerWidget();
    buttonBar->addWidget( m_startTrip );
    buttonBar->addWidget( m_stopTrip );
    
    WGroupBox *driverBox = new WGroupBox( "Driver" );
    driverBox->addWidget( m_driver );
    driverBox->setInline( true );
    passengerBox->setInline( true );
    
    buttonBar->addWidget( driverBox );
    buttonBar->addWidget( passengerBox );
    
    maplayout->addWidget( buttonBar, 1, 0, AlignLeft );
    maplayout->setRowStretch( 0, 1 );
    maplayout->setColumnStretch( 0, 1 );
  }//Carpool constructor
  
  
  virtual ~Carpool()
  {
    if( m_stopTrip->isEnabled() )
      endCurrentTrip();
  }//~Carpool()
  
  
  void endCurrentTrip()
  {
    if( !m_currentTrip )
      return;
    
    try
    {
      Dbo::Transaction transaction( m_dbSession );
      
      ClientSession *trip = m_currentTrip.modify();
      
      trip->endTime = now();
      trip->driver = Driver( m_driver->currentIndex() );
      trip->passengers = 0;
      for( Driver d = Driver(0); d < NumDrivers; d = Driver(d+1) )
        if( m_passengers[d]->isChecked() )
          trip->passengers |= (0x1<<d);
      
      typedef Dbo::collection<Dbo::ptr<PositionPoint> > Positions;
      typedef map<int,vector<Dbo::ptr<PositionPoint> > > ClientPosMap;
      
      //figure out if this is a morning, or evening event
      WDateTime earliest( trip->endTime.date() ), latest(trip->endTime.date() );
      
      const bool toWork = (trip->startTime.time().hour() < 12);
      
      if( toWork )
      {
        latest = latest.addSecs( 12*60*60 );
      }else
      {
        earliest = earliest.addSecs( 12*60*60 );
        latest = latest.addSecs( 24*60*60 );
      }//if( toWork ) / else
      
      const char *query = "SELECT pp FROM PositionPoint pp";
      Positions points = m_dbSession.query< Dbo::ptr<PositionPoint> >( query )
                                    .where("Time Between ? AND ?")
                                    .orderBy( "Time" )
                                    .bind( earliest )
                                    .bind( latest );
      
      ClientPosMap client_to_pos;  //This is only for development, can be deleted
      vector<Dbo::ptr<PositionPoint> > positions, drivepath;
      for( Positions::iterator i = points.begin(); i != points.end(); ++i )
      {
        positions.push_back( *i );
        client_to_pos[(*i)->ClientSessionPtr.id()].push_back( *i );
      }//for( iterate over PositionPoints to store them all in memmorry )

      cerr << "Got " << client_to_pos.size() << " trips, with a total of "
           << points.size() << " points" << endl;

      //Remove overlapping time regions from different sessions in positions
      vector<Dbo::ptr<PositionPoint> >::iterator iter, iter2;
      for( iter = positions.begin(); iter != positions.end(); ++iter )
      {
        const int currentId = (*iter)->ClientSessionPtr.id();
        for( iter2 = iter; iter2 != positions.end(); ++iter2 )
        {
          if( (*iter2)->ClientSessionPtr.id() == currentId )
          {
            iter = iter2;
            drivepath.push_back( *iter2 );
          }
        }//for( go through and get all locations from currentId into )
      }//for( iter = positions.begin(); iter != positions.end(); ++iter )
      
      
      //XXX - all logic bellow here in this function is unchecked
      
      TripSummarry *summary = NULL;
      Dbo::ptr<TripSummarry> sumptr;
      
      //Check to see if
      query = "SELECT ts FROM TripSummarry ts";
      sumptr = m_dbSession.query< Dbo::ptr<TripSummarry> >( query )
                          .where("StartTime Between ? AND ?")
                          .bind( earliest ).bind( latest )
                          .limit(1);
      
      if( sumptr )
      {
        summary = sumptr.modify();
        summary->recordedDistance = summary->totalTime = 0.0;
        summary->minSpeed = summary->avrgSpeed = summary->maxSpeed = 0.0;
        cerr << "Recalcuating existing TripSummarry" << endl;
      }else
      {
        cerr << "Calculating new TripSummarry" << endl;
        summary = new TripSummarry();
        sumptr.reset( summary );
      }//if( !sumptr )
      
      if( toWork )
        summary->direction = "To Work";
      else
        summary->direction = "From Work";
      
      summary->driver = to_str( trip->driver );
      
      for( Driver d = Driver(0); d < NumDrivers; d = Driver(d+1) )
      {
        if( (d!=trip->driver) && (trip->passengers>>d & (0x1<<d)) )
        {
          if( !summary->passengers.empty() )
            summary->passengers += ", ";
          summary->passengers += to_str( d );
        }//if( d != trip->driver )
      }//for( Driver d = Driver(0); d < NumDrivers; d = Driver(d+1) )
      
      //go through and find point nearest the beggining and end of the carpool
      //  trip, and then create the averages
      
      const WGoogleMap::Coordinate parknride( 37.794749, -122.207144 );
      const WGoogleMap::Coordinate gaurdshack( 37.679777, -121.717378 );
      

      vector<Dbo::ptr<PositionPoint> >::iterator startpoint = drivepath.end();
      vector<Dbo::ptr<PositionPoint> >::iterator endpoint = drivepath.end();
      
      double nearest_start = 99999999.9, nearest_end = 99999999.9;  //in km
      
      for( iter = drivepath.begin(); iter != drivepath.end(); ++iter )
      {
        WGoogleMap::Coordinate pos( (*iter)->latitude, (*iter)->longitude );
        const double distToStart = pos.distanceTo( parknride );
        const double distToWork = pos.distanceTo( gaurdshack );
        if( (distToStart < nearest_start) && (distToStart < 1.0) )
        {
          nearest_start = distToStart;
          startpoint = iter;
        }
        
        if( (distToWork < nearest_end) && (distToWork < 1.0) )
        {
          nearest_end = distToWork;
          endpoint = iter;
        }
      }//for( iter = drivepath.begin(); iter != drivepath.end(); ++iter )
      
      const bool gotstart = (startpoint != drivepath.end());
      const bool gotfinish = (endpoint != drivepath.end());
      
      if( !gotstart )
        cerr << "Couldnt find point near park-n-ride" << endl;
      if( !gotfinish )
        cerr << "Couldnt find point near gaurds shack" << endl;
      
      if( !gotstart || !gotfinish )
        return;
      
      if( toWork )
        swap( startpoint, endpoint );
      
      summary->startTime = (*startpoint)->time;
      summary->endTime = (*endpoint)->time;
      summary->totalTime = summary->startTime.secsTo( summary->endTime ) / 60.0;
      
      ++endpoint;
      int lastId = -99999;
      WGoogleMap::Coordinate prevPos;
      WDateTime prevTime;
      double recordedTime = 0.0;
      for( iter = startpoint; iter != endpoint; ++iter )
      {
        WGoogleMap::Coordinate pos( (*iter)->latitude, (*iter)->longitude );
        const int id = (*iter)->ClientSessionPtr.id();
        
        if( id == lastId )
        {
          const double distance = 0.621371 * pos.distanceTo( prevPos );
          summary->recordedDistance += distance;
          const double dt = (double)prevTime.secsTo( (*iter)->time ) / 3600.0;
          recordedTime += dt;
          const double speed = distance / dt;
          summary->minSpeed = min( summary->minSpeed, speed );
          summary->maxSpeed = max( summary->maxSpeed, speed );
        }//if( id == lastId )
        
        lastId = id;
        prevPos = pos;
        prevTime = (*iter)->time;
      }//for( iter = startpoint; iter != endpoint; ++iter )
      
      summary->avrgSpeed = summary->recordedDistance / recordedTime;
      
      m_dbSession.add( summary );
      
      transaction.commit();
    }catch( std::exception &e )
    {
      cerr << "Failed to save m_currentTrip: " << e.what() << endl;
    }
  }//void endCurrentTrip()
  
  
  void handleDriverChange()
  {
    for( Driver d = Driver(0); d < NumDrivers; d = Driver(d+1) )
      m_passengers[d]->show();
    m_passengers[m_driver->currentIndex()]->hide();
  }//void handleDriverChange()
  
  
  void setLocationFromGps( double latitude, double longitude, double altitude,
                           double posacc, double altacc, double timestamp )
  {
    if( isinf(latitude) || isnan(latitude)
       || isinf(longitude) || isnan(longitude) )
    {
      cerr << "Got a invalid position at " << now().toString() << endl;
      return;
    }
    
    const WDateTime currentTime = now();
    
    PositionPoint *pos = new PositionPoint();
    Dbo::ptr<PositionPoint> posptr( pos );
    
    pos->latitude = latitude;
    pos->longitude = longitude;
    pos->locationUncert = posacc;
    pos->ClientSessionPtr = m_currentTrip;
    
    namespace pt = boost::posix_time;
    const time_t gpstimestamp = static_cast<time_t>( timestamp / 1000.0 );
    const pt::ptime utc = pt::from_time_t( gpstimestamp );
    const pt::ptime local = from_utc_to_ca_local_time( utc );
    pos->time = WDateTime::fromPosixTime( local );
    
    try
    {
      Dbo::Transaction transaction( m_dbSession );
      m_dbSession.add( posptr );
      transaction.commit();
    }catch( exception &e )
    {
      cerr << "Failed to add position pointer to database" << endl;
    }//try / catch

    const int second_since_push = m_lastMapPush.secsTo(currentTime);
    if( second_since_push >= PUSH_UPDATE_DT )
    {
      m_map->clearOverlays();
      WGoogleMap::Coordinate coord( latitude, longitude );
      m_currentPath.push_back( coord );
      m_map->addPolyline( m_currentPath, WColor(red), 2, 1.0 );
      m_map->setCenter( coord );
      
      double distance = 0.0;
      for( size_t i = 1; i < m_currentPath.size(); ++i )
        distance += m_currentPath[i-1].distanceTo( coord );
      distance *= 0.621371;
      
      double time_hrs = 0.0;
      if( m_currentTrip )
        time_hrs = m_currentTrip->startTime.secsTo( currentTime ) / 3600.0;
      
      double last_dist = 0.0;
      if( m_currentPath.size() > 2 )
        last_dist = m_currentPath[m_currentPath.size()-2].distanceTo( coord );
      last_dist *= 0.621371;
      
      double last_time_hrs = second_since_push / 3600.0;
      
      const double mph = distance / time_hrs;
      const double last_mph = last_dist / last_time_hrs;
      
      stringstream htmlstrm;
      htmlstrm << "<table>"
               << "<tr><th>Distance</th><td>&nbsp;&nbsp;"
               << std::fixed << setprecision(1)<< distance << " miles</td></tr>"
               << "<tr><th>Current Speed</th><td>&nbsp;&nbsp;"
               << setprecision(1) << last_mph << " mph</td><tr>"
               << "<tr><th>Avrage Speed</th><td>&nbsp;&nbsp;"
               << setprecision(1) << mph << " mph</td><tr>"
               << "</table>";
      m_map->openInfoWindow( coord, htmlstrm.str() );
      m_lastMapPush = currentTime;
    }//if( m_lastMapPush.secsTo( currentTime ) > 60 )
  }//setLocationFromGps(...)
  
  
  void stopTrip()
  {
    endCurrentTrip();
    m_startTrip->enable();
    m_stopTrip->disable();
    doJavaScript( "Wt.WT.StopReOccurringLocation('" + id() + "');" );
  }//void stopTrip()
  
  
  void startTrip()
  {
    m_currentPath.clear();
    m_map->clearOverlays();
    m_lastMapPush = now().addSecs( - PUSH_UPDATE_DT - 1 );
    m_startTrip->disable();
    m_stopTrip->enable();
    
    ClientSession *info = new ClientSession();
    m_currentTrip.reset( info );
    info->driver = Driver( m_driver->currentIndex() );
    
    for( Driver d = Driver(0); d < NumDrivers; d = Driver(d+1) )
      if( m_passengers[d]->isChecked() )
        info->passengers |= (1<<d);
      
    try
    {
      Dbo::Transaction transaction( m_dbSession );
      m_currentTrip = m_dbSession.add( m_currentTrip );
      transaction.commit();
    }catch( exception &e )
    {
      cerr << "Failed to add current trip database" << endl;
      m_currentTrip.reset();
    }//try / catch
    
    doJavaScript( "Wt.WT.GetReOccurringLocation('" + id() + "', '" + GPS_UPDATE_DT + "');" );
  }//void getLocationFromGps()
  
};//class Carpool


Wt::WApplication *createApplication( const Wt::WEnvironment& env )
{
  WApplication *app = new WApplication( env );
  Carpool *car = new Carpool();
  
  WGridLayout *layout = new WGridLayout();
  layout->addWidget( car, 0, 0 );
  app->root()->setLayout( layout );
  
  return app;
}//WApplication *createApplication( const Wt::WEnvironment& env )


int main( int argc, char **argv )
{
  int result = Wt::WRun( argc, argv, &createApplication );

  return result;
}//int main( int argc, const char * argv[] )

